"use strict";
var Util = require('./util');
var Base = require('./base');
var Easing = require('./easing');

var RAF = window.requestAnimationFrame ||
	window.webkitRequestAnimationFrame ||
	window.mozRequestAnimationFrame ||
	window.oRequestAnimationFrame ||
	window.msRequestAnimationFrame ||
	function(callback) {
		window.setTimeout(callback, 1000 / 60);
	};

var vendors = ['webkit', 'moz', 'ms', 'o'];
var cancelRAF = window.cancelAnimationFrame;
for (var i = 0; i < vendors.length; i++) {
	if (window[vendors[i] + 'CancelAnimationFrame'] || window[vendors[i] + 'CancelRequestAnimationFrame']) {
		cancelRAF = window[vendors[i] + 'CancelAnimationFrame'] || window[vendors[i] + 'CancelRequestAnimationFrame'];
	}
}
cancelRAF = cancelRAF || window.clearTimeout;

function Bezier(x1, y1, x2, y2, epsilon) {
	var curveX = function(t) {
		var v = 1 - t;
		return 3 * v * v * t * x1 + 3 * v * t * t * x2 + t * t * t;
	};

	var curveY = function(t) {
		var v = 1 - t;
		return 3 * v * v * t * y1 + 3 * v * t * t * y2 + t * t * t;
	};

	var derivativeCurveX = function(t) {
		var v = 1 - t;
		return 3 * (2 * (t - 1) * t + v * v) * x1 + 3 * (-t * t * t + 2 * v * t) * x2;
	};

	return function(t) {

		var x = t,
			t0, t1, t2, x2, d2, i;

		// First try a few iterations of Newton's method -- normally very fast.
		for (t2 = x, i = 0; i < 8; i++) {
			x2 = curveX(t2) - x;
			if (Math.abs(x2) < epsilon) return curveY(t2);
			d2 = derivativeCurveX(t2);
			if (Math.abs(d2) < 1e-6) break;
			t2 = t2 - x2 / d2;
		}

		t0 = 0, t1 = 1, t2 = x;

		if (t2 < t0) return curveY(t0);
		if (t2 > t1) return curveY(t1);

		// Fallback to the bisection method for reliability.
		while (t0 < t1) {
			x2 = curveX(t2);
			if (Math.abs(x2 - x) < epsilon) return curveY(t2);
			if (x > x2) t0 = t2;
			else t1 = t2;
			t2 = (t1 - t0) * .5 + t0;
		}

		// Failure
		return curveY(t2);

	};

};



function Timer(cfg) {
	var self = this;
	self.cfg = Util.mix({
		easing: "linear"
	}, cfg)
}

Timer.MIN_DURATION = 1;

Util.extend(Timer, Base, {
	reset: function(cfg) {
		var self = this;
		Util.mix(self.cfg, cfg);
		self.isfinished = false;
		self.percent = 0;
		self._stop = null;
	},
	run: function() {
		var self = this;
		var duration = self.cfg.duration;
		if (duration <= Timer.MIN_DURATION) {
			self.isfinished = true;
			self.trigger("run", {
				percent: 1
			});
			self.trigger("end", {
				percent: 1
			});
		}
		if (self.isfinished) return;
		self._hasFinishedPercent = self._stop && self._stop.percent || 0;
		self._stop = null;
		self.start = Date.now();
		self.percent = 0;
		// epsilon determines the precision of the solved values
		var epsilon = (1000 / 60 / duration) / 4;
		var b = Easing[self.cfg.easing];
		self.easingFn = Bezier(b[0], b[1], b[2], b[3], epsilon);
		self._run();
	},
	_run: function() {
		var self = this;
		cancelRAF(self._raf);
		self._raf = RAF(function() {
			self.now = Date.now();
			self.duration = self.now - self.start >= self.cfg.duration ? self.cfg.duration : self.now - self.start;
			self.progress = self.easingFn(self.duration / self.cfg.duration);
			self.percent = self.duration / self.cfg.duration + self._hasFinishedPercent;
			if (self.percent >= 1 || self._stop) {
				self.percent = self._stop && self._stop.percent ? self._stop.percent : 1;
				self.duration = self._stop && self._stop.duration ? self._stop.duration : self.duration;
				var param = {
					percent: self.percent
				};
				self.trigger("stop", param);
				if (self.percent >= 1) {
					self.isfinished = true;
					self.trigger("end", {
						percent: 1
					});
				}
				return;
			}
			self.trigger("run", {
				percent: self.progress,
				originPercent:self.percent
			});
			self._run();
		})
	},
	stop: function() {
		var self = this;
		self._stop = {
			percent: self.percent,
			now: self.now
		};
		cancelRAF(self._raf)
	}
});


if (typeof module == 'object' && module.exports) {
	module.exports = Timer;
}
